/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using DaveSexton.Controls;

namespace DaveSexton.DocProject.Sandcastle.ContentManagement
{
	public enum ContentEditorTab
	{
		Design, 
		Source, 
		Preview
	}

	[DefaultProperty("TemplateHtml"), DefaultEvent("DesignerReady")]
	public sealed partial class XmlContentEditor : UserControl
	{
		#region Public Properties
		[Category("Appearance")]
		public ContentEditorTab SelectedTab
		{
			get
			{
				if (tabs.SelectedTab == designTabPage)
					return ContentEditorTab.Design;
				else if (tabs.SelectedTab == sourceTabPage)
					return ContentEditorTab.Source;
				else
					return ContentEditorTab.Preview;
			}
			set
			{
				if (!Enum.IsDefined(typeof(ContentEditorTab), value))
					throw new InvalidEnumArgumentException("value", (int) value, typeof(ContentEditorTab));

				switch (value)
				{
					case ContentEditorTab.Design:
						tabs.SelectedTab = designTabPage;
						break;
					case ContentEditorTab.Source:
						tabs.SelectedTab = sourceTabPage;
						break;
					default:
						tabs.SelectedTab = previewTabPage;
						break;
				}
			}
		}

		/// <remarks>
		/// The <see cref="SourceHtmlChanged"/> event is not raised when this property is assigned at runtime.
		/// It's only raised when the user edits the document in design view or source view.
		/// </remarks>
		[Category("Data"), DefaultValue("")]
		public string SourceHtml
		{
			get
			{
				if (tabs.SelectedTab == designTabPage)
				{
					if (designHtmlEditor.IsReady)
						return designHtmlEditor.Body ?? string.Empty;
					else
						return string.Empty;
				}
				else
					return sourceTextBox.Text;
			}
			set
			{
				if (!designHtmlEditor.IsReady)
					throw new InvalidOperationException(Resources.Errors.ContentEditorNotReady);

				if (value == null)
					value = string.Empty;

				isValid = null;
				sourceXml = null;

				ignoreChangeEvents = true;
				changingSourceHtml = true;

				sourceTextBox.Text = null;
				designHtmlEditor.Body = value;

				tabs.SelectedTab = designTabPage;

				// NOTE: ignoreChangeEvents is reset in the HtmlChanged event handler since the WebBrowser control
				// will only load the document after the UI thread has been freed.

				// NOTE: Do not raise OnSourceHtmlChanged event since it only applies to changes made by the user
			}
		}

		[Category("Data"), DefaultValue("")]
		public string SourceText
		{
			get
			{
				if (tabs.SelectedTab == designTabPage)
				{
					if (designHtmlEditor.IsReady)
						return designHtmlEditor.Document.Body.InnerText ?? string.Empty;
					else
						return string.Empty;
				}
				else
					return sourceTextBox.Text;
			}
			set
			{
				if (value == null)
					value = string.Empty;

				ignoreChangeEvents = true;

				sourceTextBox.Text = value;

				tabs.SelectedTab = sourceTabPage;

				ignoreChangeEvents = false;

				// NOTE: Do not raise OnSourceHtmlChanged event since it only applies to changes made by the user
			}
		}

		[Browsable(false)]
		public string DesignerDocumentHtml
		{
			get
			{
				if (designHtmlEditor.IsReady)
					return designHtmlEditor.Html ?? string.Empty;
				else
					return string.Empty;
			}
		}

		[Category("Data"), DefaultValue("")]
		public string PreviewHtml
		{
			get
			{
				HtmlDocument document = previewWebBrowser.Document;

				if (document != null)
				{
					using (HtmlStream stream = new HtmlStream(document.DomDocument, false, HtmlStreamAccess.Read))
					{
						return stream.ReadToEnd() ?? string.Empty;
					}
				}
				else
					return string.Empty;
			}
			set
			{
				if (value == null)
					value = string.Empty;

				HtmlDocument document = previewWebBrowser.Document;

				if (document == null)
					throw new InvalidOperationException(Resources.Errors.ContentEditorNotReady);

				using (HtmlStream stream = new HtmlStream(document.DomDocument, false, HtmlStreamAccess.Write))
				{
					stream.Write(value);
				}
			}
		}

		/// <summary>
		/// Gets or sets the designer's HTML template as raw HTML markup.
		/// </summary>
		/// <remarks>
		/// The template must contain valid HTML markup.
		/// <para>
		/// Optionally, a single <c>$BODY$</c> token that indicates the placement of the body HTML may be specified for when the <see cref="Body"/> 
		/// property is assigned.
		/// </para>
		/// </remarks>
		[Category("Appearance"), Description("Specifies the designer's HTML template as raw HTML markup.  " +
		 "Optionally, a single $BODY$ token that indicates the placement of the body HTML can be included for when the Body property is assigned."),
		RefreshProperties(RefreshProperties.All)]
		[Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public string TemplateHtml
		{
			get
			{
				return designHtmlEditor.TemplateHtml;
			}
			set
			{
				designHtmlEditor.TemplateHtml = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool SourceXmlRequired
		{
			get
			{
				return sourceXmlRequired;
			}
			set
			{
				sourceXmlRequired = value;
			}
		}

		[Category("Behavior")]
		public Importer Importer
		{
			get
			{
				return importer;
			}
			set
			{
				importer = value;
			}
		}

		[Browsable(false)]
		public System.Xml.XmlNamespaceManager Namespaces
		{
			get
			{
				return namespaces;
			}
			set
			{
				namespaces = value;
			}
		}

		[Browsable(false)]
		public bool IsSourceHtmlValidXml
		{
			get
			{
				if (isValid.HasValue && isValid.Value)
					return true;
				else if (!isValid.HasValue)
				{
					string html = null;

					if (tabs.SelectedTab == designTabPage)
					{
            if (designHtmlEditor.IsReady)
              html = designHtmlEditor.Body;
            else
              return false;
					}
					else
						html = sourceTextBox.Text;

					try
					{
						if (html == null)
							// null has special meaning for sourceXml that isn't appropriate here
							sourceXml = string.Empty;
						else
						{
							if (tabs.SelectedTab == designTabPage)
								sourceXml = displayManager.ConvertHtmlToXml(html, !doNotIncludeDocPrefix, namespaces);
							else
								sourceXml = displayManager.ConvertHtmlToXml(html, false, namespaces);
						}

						SetInvalidXmlError(null);
						isValid = true;
					}
					catch (ArgumentException ex)
					{
						SetInvalidXmlError(ex.Message);
					}
				}

				return isValid.Value;
			}
		}

		[Browsable(false)]
		public bool IsDesignerReady
		{
			get
			{
				return designHtmlEditor.IsReady;
			}
		}

		[Browsable(false)]
		public string SourceHtmlAsXmlWithoutDocPrefix
		{
			get
			{
				string value = null;

				if (tabs.SelectedTab == designTabPage)
					isValid = null;

				if (sourceXml == null || !isValid.HasValue)
				{
					doNotIncludeDocPrefix = true;

					try
					{
						if (!IsSourceHtmlValidXml)
							sourceXml = null;

						value = sourceXml;

						sourceXml = null;
						isValid = null;
					}
					finally
					{
						doNotIncludeDocPrefix = false;
					}
				}
				else
					value = sourceXml;

				return value;
			}
		}

		[Browsable(false)]
		public string SourceHtmlAsXml
		{
			get
			{
				if (sourceXml == null || !isValid.HasValue)
				{
					if (!IsSourceHtmlValidXml)
						sourceXml = null;
				}

				return sourceXml;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool HasChanges
		{
			get
			{
				return hasChanges;
			}
			set
			{
				hasChanges = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool DesignerEnabled
		{
			get
			{
				return designerEnabled;
			}
			set
			{
				designerEnabled = value;

				if (value && !tabs.TabPages.Contains(designTabPage))
				{
					tabs.TabPages.Insert(0, designTabPage);

					// A bug in the TabPagesCollection class will prevent Insert from working before the control is loaded (no exception is thrown however).
					// Insert is attempted first, but if it doesn't work then all of the tab pages have to be added in the correct order using Add and Remove, 
					// which work fine even before the control has been loaded.
					// NOTE: Calling tabs.Controls.SetChildIndex does not work either.
					if (!tabs.TabPages.Contains(designTabPage))
					{
						tabs.TabPages.Add(designTabPage);

						tabs.TabPages.Remove(sourceTabPage);
						tabs.TabPages.Add(sourceTabPage);

						if (tabs.TabPages.Contains(previewTabPage))
						{
							tabs.TabPages.Remove(previewTabPage);
							tabs.TabPages.Add(previewTabPage);
						}
					}
				}

				if (designHtmlEditor.IsReady)
					UpdateDesignerState();

				if (!value && !allowDesignerWhenDisabled && tabs.TabPages.Contains(designTabPage))
					tabs.TabPages.Remove(designTabPage);
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool PreviewEnabled
		{
			get
			{
				return previewTabPage.Parent != null;
			}
			set
			{
				if (value)
					tabs.TabPages.Add(previewTabPage);
				else
					tabs.TabPages.Remove(previewTabPage);
			}
		}

		[Category("Behavior"), DefaultValue(false)]
		public bool AllowDesignerTabActiveWhenDisabled
		{
			get
			{
				return allowDesignerWhenDisabled;
			}
			set
			{
				allowDesignerWhenDisabled = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public XmlDocumentationDisplayManager DisplayManager
		{
			get
			{
				return displayManager;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");
				else if (value == displayManager)
					return;

				displayManager = value;

				if (designHtmlEditor.IsReady)
					// NOTE: The source HTML must be reset when the display manager changes (the "ready" state will be lost)
					SourceHtml = string.Empty;

				// NOTE: Setting the template HTML also causes the editor to lose its "ready" state
				designHtmlEditor.TemplateHtml = XmlDocumentationDisplayManager.TopicDesignerTemplateHtml;

				foreach (Uri styleSheet in displayManager.StyleSheets)
					designHtmlEditor.AddTemplateStyleSheet(styleSheet);

				WaitForReady();
			}
		}
		#endregion

		#region Private / Protected
		private bool? isValid;
		private bool ignoreChangeEvents, sourceXmlRequired = true, hasChanges, designerEnabled = true, allowDesignerWhenDisabled, changingSourceHtml, switchingToSourceForError;
		private bool doNotIncludeDocPrefix;
		private string sourceXml;
		private Importer importer;
		private XmlDocumentationDisplayManager displayManager;
		private System.Xml.XmlNamespaceManager namespaces;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlContentEditor" /> class.
		/// </summary>
		public XmlContentEditor()
		{
			InitializeComponent();

			// For some unknown reason the designer will inappropriately set these properties
			// to false even when they're true at design-time, although it doesn't happen every
			// time the designer is used.  The properties are set to true here as a work-around.
			this.designHtmlEditorToolStrip.ColumnAlignmentVisible = true;
			this.designHtmlEditorToolStrip.FontSizeSelectorVisible = true;
			this.designHtmlEditorToolStrip.FontStylesVisible = true;
			this.designHtmlEditorToolStrip.TagGlyphControlVisible = true;
			this.designHtmlEditorToolStrip.TextAlignmentVisible = true;
		}
		#endregion

		#region Methods
		public void WaitForReady()
		{
			while (!designHtmlEditor.IsReady)
				Application.DoEvents();
		}

		public void PasteSourceText(string text)
		{
			sourceTextBox.SelectedText = text;
		}

		public void AddTemplateStyleSheet(Uri file)
		{
			designHtmlEditor.AddTemplateStyleSheet(file);
		}

		public void SetInvalidXmlError(string message)
		{
			if (!string.IsNullOrEmpty(message))
			{
				errors.SetError(tabs, message);
				isValid = false;
				sourceXml = null;
			}
			else
				errors.SetError(tabs, null);
		}

		private void SynchronizeSourceAndDesign(bool initializingSourceHtml)
		{
			if (ignoreChangeEvents && !initializingSourceHtml)
				return;

			ignoreChangeEvents = true;

			try
			{
				if (tabs.SelectedTab == sourceTabPage)
				{
					if (designerEnabled)
					{
						sourceTextBox.Text = (sourceXmlRequired)
							? sourceXml = displayManager.ConvertHtmlToXml(designHtmlEditor.Body, false, namespaces)
							: designHtmlEditor.Body;
					}

					if (!initializingSourceHtml)
						ignoreChangeEvents = false;
				}
				else if (!initializingSourceHtml)
				{
					designHtmlEditor.Body = (sourceXmlRequired)
						? sourceXml = displayManager.ConvertHtmlToXml(sourceTextBox.Text, true, namespaces)
						: sourceTextBox.Text;

					// ignoreChangeEvents is reset in the HtmlChanged event handler since the WebBrowser control
					// will only load the document after the UI thread has been freed.
				}

				if (sourceXmlRequired)
				{
					SetInvalidXmlError(null);

					if (sourceXml != null)
						isValid = true;
				}
			}
			catch (ArgumentException ex)
			{
				SetInvalidXmlError(ex.Message);
			}
		}

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
		private void UpdateDesignerState()
		{
			if (designHtmlEditor.IsReady)
				designHtmlEditor.DefaultEditableElement.SetAttribute("contentEditable", (Enabled && designerEnabled).ToString().ToLowerInvariant());

			if (Enabled)
				designHtmlEditorToolStrip.Enabled = designerEnabled;

			if (!designerEnabled && !allowDesignerWhenDisabled && tabs.SelectedTab == designTabPage)
				tabs.SelectedTab = sourceTabPage;
		}
		#endregion

		#region Events
		private readonly object PreviewEvent = new object();
		private readonly object DesignerReadyEvent = new object();
		private readonly object SourceHtmlChangedEvent = new object();
		private readonly object DesignerDragDropEvent = new object();
		private readonly object DesignerDragOverEvent = new object();
		private readonly object DesignerDragLeaveEvent = new object();
		private readonly object SourceDragDropEvent = new object();
		private readonly object SourceDragOverEvent = new object();
		private readonly object SourceDragLeaveEvent = new object();
		private readonly object SourceDragEnterEvent = new object();
	
		/// <summary>
		/// Event raised when the mouse drags content into the <see cref="source" /> <see cref="TextBox"/>.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content into the source text box.")]
		public event DragEventHandler SourceDragEnter
		{
			add
			{
				lock (SourceDragEnterEvent)
				{
					Events.AddHandler(SourceDragEnterEvent, value);
				}
			}
			remove
			{
				lock (SourceDragEnterEvent)
				{
					Events.RemoveHandler(SourceDragEnterEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragEnter" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragEnter(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragEnterEvent)
			{
				handler = (DragEventHandler) Events[SourceDragEnterEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content away from the <see cref="source" /> <see cref="TextBox"/>.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content away from the source text box.")]
		public event EventHandler SourceDragLeave
		{
			add
			{
				lock (SourceDragLeaveEvent)
				{
					Events.AddHandler(SourceDragLeaveEvent, value);
				}
			}
			remove
			{
				lock (SourceDragLeaveEvent)
				{
					Events.RemoveHandler(SourceDragLeaveEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragLeave" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragLeave(EventArgs e)
		{
			EventHandler handler = null;

			lock (SourceDragLeaveEvent)
			{
				handler = (EventHandler) Events[SourceDragLeaveEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content over the <see cref="source" /> <see cref="TextBox"/>.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content over the source text box.")]
		public event DragEventHandler SourceDragOver
		{
			add
			{
				lock (SourceDragOverEvent)
				{
					Events.AddHandler(SourceDragOverEvent, value);
				}
			}
			remove
			{
				lock (SourceDragOverEvent)
				{
					Events.RemoveHandler(SourceDragOverEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragOver" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragOver(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragOverEvent)
			{
				handler = (DragEventHandler) Events[SourceDragOverEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an object is dropped on the source text box.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when an object is dropped on the source text box.")]
		public event DragEventHandler SourceDragDrop
		{
			add
			{
				lock (SourceDragDropEvent)
				{
					Events.AddHandler(SourceDragDropEvent, value);
				}
			}
			remove
			{
				lock (SourceDragDropEvent)
				{
					Events.RemoveHandler(SourceDragDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="DragEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragDrop(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragDropEvent)
			{
				handler = (DragEventHandler) Events[SourceDragDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content away from the <see cref="designHtmlEditor" />.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content away from the body element.")]
		public event EventHandler<HtmlEditorEventArgs> DesignerDragLeave
		{
			add
			{
				lock (DesignerDragLeaveEvent)
				{
					Events.AddHandler(DesignerDragLeaveEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragLeaveEvent)
				{
					Events.RemoveHandler(DesignerDragLeaveEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragLeave" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragLeave(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DesignerDragLeaveEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DesignerDragLeaveEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content over the designer.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content over the designer.")]
		public event EventHandler<HtmlEditorEventArgs> DesignerDragOver
		{
			add
			{
				lock (DesignerDragOverEvent)
				{
					Events.AddHandler(DesignerDragOverEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragOverEvent)
				{
					Events.RemoveHandler(DesignerDragOverEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragOver" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragOver(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DesignerDragOverEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DesignerDragOverEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an object is dropped on the designer.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when an object is dropped on the designer.")]
		public event EventHandler<HtmlEditorDragDropEventArgs> DesignerDragDrop
		{
			add
			{
				lock (DesignerDragDropEvent)
				{
					Events.AddHandler(DesignerDragDropEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragDropEvent)
				{
					Events.RemoveHandler(DesignerDragDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragDrop(HtmlEditorDragDropEventArgs e)
		{
			EventHandler<HtmlEditorDragDropEventArgs> handler = null;

			lock (DesignerDragDropEvent)
			{
				handler = (EventHandler<HtmlEditorDragDropEventArgs>) Events[DesignerDragDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="SourceHtml"/> property value is changed by the user in source view or design view.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the SourceHtml property value is changed by the user in source view or design view.")]
		public event EventHandler<HtmlChangedEventArgs> SourceHtmlChanged
		{
			add
			{
				lock (SourceHtmlChangedEvent)
				{
					Events.AddHandler(SourceHtmlChangedEvent, value);
				}
			}
			remove
			{
				lock (SourceHtmlChangedEvent)
				{
					Events.RemoveHandler(SourceHtmlChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceHtmlChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
    private void OnSourceHtmlChanged(HtmlChangedEventArgs e)
		{
			EventHandler<HtmlChangedEventArgs> handler = null;

			lock (SourceHtmlChangedEvent)
			{
				handler = (EventHandler<HtmlChangedEventArgs>) Events[SourceHtmlChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the designer is ready.
		/// </summary>
		[Category("State Changed")]
		[Description("Event raised when the designer is ready.")]
		public event EventHandler DesignerReady
		{
			add
			{
				lock (DesignerReadyEvent)
				{
					Events.AddHandler(DesignerReadyEvent, value);
				}
			}
			remove
			{
				lock (DesignerReadyEvent)
				{
					Events.RemoveHandler(DesignerReadyEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerReady" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerReady(EventArgs e)
		{
			EventHandler handler = null;

			lock (DesignerReadyEvent)
			{
				handler = (EventHandler) Events[DesignerReadyEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the Preview tab becomes active.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when the Preview tab becomes active.")]
		public event EventHandler Preview
		{
			add
			{
				lock (PreviewEvent)
				{
					Events.AddHandler(PreviewEvent, value);
				}
			}
			remove
			{
				lock (PreviewEvent)
				{
					Events.RemoveHandler(PreviewEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="Preview" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnPreview(EventArgs e)
		{
			EventHandler handler = null;

			lock (PreviewEvent)
			{
				handler = (EventHandler) Events[PreviewEvent];
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion

		#region Event Handlers
		private void designHtmlEditor_EditorReady(object sender, EventArgs e)
		{
			UpdateDesignerState();

			OnDesignerReady(e);
		}

		private void designHtmlEditor_DragLeave(object sender, DaveSexton.Controls.HtmlEditorEventArgs e)
		{
			OnDesignerDragLeave(e);
		}

		private void designHtmlEditor_DragOver(object sender, DaveSexton.Controls.HtmlEditorEventArgs e)
		{
			OnDesignerDragOver(e);
		}

		private void designHtmlEditor_DragDrop(object sender, DaveSexton.Controls.HtmlEditorDragDropEventArgs e)
		{
			OnDesignerDragDrop(e);
		}

		private void designHtmlEditor_HtmlChanged(object sender, HtmlChangedEventArgs e)
		{
			// NOTE: Since events in the editor's tool strip may change the HTML without raising HtmlChanged, 
			// changes are also handled in the control's OnValidating method.

			if (!ignoreChangeEvents)
			{
				isValid = null;
				hasChanges = true;

				OnSourceHtmlChanged(e);
			}
			else
			{
				if (changingSourceHtml)
				{
					if (!hasChanges && importer != null && importer.Rebase != ImporterRebaseAction.None)
						importer.RebaseImagesToTargetDirectory(designHtmlEditor.Document);

					SynchronizeSourceAndDesign(true);

					UpdateDesignerState();
				}

				designHtmlEditor.IsDirty = false;
				ignoreChangeEvents = false;

				if (changingSourceHtml)
				{
					changingSourceHtml = false;
					OnSourceHtmlChanged(new HtmlChangedEventArgs(HtmlChangedReason.DocumentLoaded));
				}
			}
		}

		protected override void OnEnabledChanged(EventArgs e)
		{
			base.OnEnabledChanged(e);

			UpdateDesignerState();
		}

		private void tabs_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (tabs.SelectedTab == previewTabPage)
				OnPreview(e);
			else if (!switchingToSourceForError)
				SynchronizeSourceAndDesign(false);
		}

		private void tabs_Deselecting(object sender, TabControlCancelEventArgs e)
		{
			if (!changingSourceHtml && sourceXmlRequired && !IsSourceHtmlValidXml)
				e.Cancel = !switchingToSourceForError && e.TabPage != sourceTabPage;
		}

		private void tabs_Selecting(object sender, TabControlCancelEventArgs e)
		{
			if (e.TabPage == designTabPage)
				e.Cancel = !designerEnabled && !allowDesignerWhenDisabled;
		}

		protected override void OnValidating(CancelEventArgs e)
		{
			System.Diagnostics.Debug.WriteLine("Validating", "XmlContentEditor");

			if (designHtmlEditor.IsDirty)
			{
				isValid = null;
				hasChanges = true;
				designHtmlEditor.IsDirty = false;

				OnSourceHtmlChanged(new HtmlChangedEventArgs(HtmlChangedReason.UserEdit));
			}

			if (sourceXmlRequired && !IsSourceHtmlValidXml)
			{
				if (tabs.SelectedTab == designTabPage)
				{
					switchingToSourceForError = true;

					// show raw HTML markup as the source since it cannot be transformed into valid XML
					sourceTextBox.Text = designHtmlEditor.Body;

					tabs.SelectedTab = sourceTabPage;

					switchingToSourceForError = false;
				}
				else
					e.Cancel = true;
			}

			base.OnValidating(e);
		}

		private void sourceTextBox_TextChanged(object sender, EventArgs e)
		{
			if (!ignoreChangeEvents)
			{
				isValid = null;
				hasChanges = true;
			}
		}

		private void sourceTextBox_Validating(object sender, CancelEventArgs e)
		{
			System.Diagnostics.Debug.WriteLine("Source text validating", "XmlContentEditor");

			if (hasChanges && !ignoreChangeEvents)
			{
				if (sourceXmlRequired && !IsSourceHtmlValidXml)
					e.Cancel = true;
				else
				{
					OnSourceHtmlChanged(new HtmlChangedEventArgs(HtmlChangedReason.UserEdit));
				}
			}
		}

		private void sourceTextBox_DragDrop(object sender, DragEventArgs e)
		{
			OnSourceDragDrop(e);
		}

		private void sourceTextBox_DragEnter(object sender, DragEventArgs e)
		{
			OnSourceDragEnter(e);
		}

		private void sourceTextBox_DragOver(object sender, DragEventArgs e)
		{
			OnSourceDragOver(e);
		}

		private void sourceTextBox_DragLeave(object sender, EventArgs e)
		{
			OnSourceDragLeave(e);
		}
		#endregion
	}
}
